package com.thoughtworks.jspecsupport;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.thoughtworks.jspecsupport.settings.JSpecSupportSettings;
import com.thoughtworks.jspecsupport.settings.SettingsValidator;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;


public class RunnerGenerator {

    private FileFinder fileFinder;
    private SettingsValidator settingsValidator;

    public RunnerGenerator() {
        this.fileFinder = new FileFinderImpl();
        settingsValidator = new SettingsValidator();
    }

    RunnerGenerator(FileFinder fileFinder, SettingsValidator settingsValidator) {
        this.fileFinder = fileFinder;
        this.settingsValidator = settingsValidator;
    }

    public String generate(List<String> testFileSources, List<String> testFilePaths, String prodJsFolder, String testLibFolder) {
        settingsValidator.validate(prodJsFolder, testLibFolder);
        StringBuilder runnerSource = new StringBuilder();

        List<String> loadedFiles = gatherLoadedFiles(testFileSources);

        for (String loadedFile : loadedFiles) {
            runnerSource.append(findFile(loadedFile, prodJsFolder, testLibFolder));
            runnerSource.append("\n");
        }

        runnerSource.append("JSpec");
        for (String testFilePath : testFilePaths) {
            runnerSource.append(".exec('" + testFilePath + "')");
        }
        runnerSource.append(".run({reporter: JSpec.reporters.Terminal}).report();");

        return runnerSource.toString();
    }

    private List<String> gatherLoadedFiles(List<String> testFileSources) {
        List<String> imports = new ArrayList<String>();
        try {
            for (String testFileSource : testFileSources) {
                BufferedReader reader = new BufferedReader(new StringReader(testFileSource));
                String line = reader.readLine();
                while (line != null) {
                    LoadStatementMatcher loadStmtMatcher = new LoadStatementMatcher();
                    if (loadStmtMatcher.isMatch(line)) {
                        String fileToLoad = loadStmtMatcher.match(line);
                        if (!imports.contains(fileToLoad)) {
                            imports.add(fileToLoad);
                        }
                    }
                    line = reader.readLine();
                }
            }
        } catch (IOException ignore) {
        }
        return imports;
    }

    private String findFile(String importedFile, String prodJsFolder, String testLibFolder) {
        String fileFullPath = findFileUnderProdOrTestLibFolders(importedFile, prodJsFolder, testLibFolder);
        fileFullPath = fileFullPath.replace("\\", "/"); // hack for windows

        return "load('" + fileFullPath + "');";
    }

    public String generate(List<VirtualFile> files, Project project) throws IOException {
        ArrayList<String> testFileSources = new ArrayList<String>();
        ArrayList<String> testFilePaths = new ArrayList<String>();
        for (VirtualFile file : files) {
            testFileSources.add(new String(file.contentsToByteArray()));
            testFilePaths.add(file.getPath());
        }
        return generate(testFileSources, testFilePaths, getProdJsFolder(project), getTestLibFolder(project));
    }

    private String findFileUnderProdOrTestLibFolders(String importedFile, String prodFolder, String testLibFodler) {
        String foundInProdFolder = fileFinder.findFileUnderFolder(prodFolder, importedFile);
        if (foundInProdFolder != null) return foundInProdFolder;

        String foundInTestLibFolder = fileFinder.findFileUnderFolder(testLibFodler, importedFile);
        if (foundInTestLibFolder == null) throw new JsFileNotFoundException(importedFile);
        return foundInTestLibFolder;
    }

    private String getProdJsFolder(Project project) {
        return JSpecSupportSettings.getInstance(project).getProdJsFolder();
    }

    private String getTestLibFolder(Project project) {
        return JSpecSupportSettings.getInstance(project).getTestLibFolder();
    }

    private class FileFinderImpl implements FileFinder {

        public String findFileUnderFolder(String folder, String file) {
            return dofindFileUnderFolder(new File(folder), file);
        }

        private String dofindFileUnderFolder(File folder, String file) {
            for (File child : folder.listFiles()) {
                if (child.isFile() && child.getName().equals(file)) {
                    return child.getAbsolutePath();
                }
                if (child.isDirectory()) {
                    String findResult = dofindFileUnderFolder(child, file);
                    if (findResult != null) return findResult;
                }
            }
            return null;
        }
    }
}
